﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace csis.ShellClasses
{
	public class CommandLineInterpreter
	{
		public	CommandLineInterpreter(IEnumerable<string> args)
		{
			if (args == null)
			{
				throw new ArgumentNullException("args");
			}

			this.args = args;
			this.options = new ShellOptions();

			this.enumerator = this.args.GetEnumerator();

			this.Interpret();
		}

		private readonly IEnumerable<string> args;
		private readonly ShellOptions options;
		private readonly IEnumerator<string> enumerator;

		private List<string> errors = new List<string>();

		public ShellOptions ShellOptions
		{
			get { return this.options; }
		}

		private void Interpret()
		{
			if (!this.args.Any())
			{
				this.options.Usage = true;
				return;
			}

			while (enumerator.MoveNext())
			{
				var arg = this.enumerator.Current;

				if (arg[0] == '/' || arg[0] == '-')
				{
					Switch();
				}
				else
				{
					this.Error("Stray argument: '" + arg + "'");
				}
			}

			var io = this.options.ImmediatorOptions;

			if (io.FirstFileAsArgument && io.AllFilesAsArguments)
			{
				Error("Cannot use /afs and /aas together");
			}
			if (io.AllFilesEnvVar != null && io.AllFilesEnvVar == io.FirstFileEnvVar)
			{
				Error("/envfs and /envas cannot use the same environment variable");
			}
		}

		private void Switch()
		{
			var arg = this.enumerator.Current.Substring(1);

			if (arg == "nowait")
			{
				Trace.WriteLine("No Wait enabled");
				this.options.ImmediatorOptions.NoWait = true;
			}
			else if (arg == "w")
			{
				if (!this.enumerator.MoveNext())
				{
					Error("No warning level specified after /w switch");
					return;
				}
				int level = Int32.Parse(this.enumerator.Current);
				Trace.WriteLine("Warning level set to '" + level + "'");
				this.options.ImmediatorOptions.CompilerOptions.CompilerWarningLevel = level;
			}
			else if (arg == "r")
			{
				if (!this.enumerator.MoveNext())
				{
					Error("No assembly specified after /r switch");
					return;
				}
				var referencedAssembly = this.enumerator.Current;
				Trace.WriteLine("Referenced Assembly added: '" + referencedAssembly + "'");
				this.options.ImmediatorOptions.CompilerOptions.ReferencedAssemblies.Add(referencedAssembly);
			}
			else if (arg == "ex")
			{
				Trace.WriteLine("Usage examples requested");
				this.options.UsageExamples = true;
			}
			else if (arg == "pe")
			{
				Trace.WriteLine("Pause on errors enabled");
				this.options.PauseOnErrors = true;
			}
			else if (arg == "p")
			{
				Trace.WriteLine("Pause after script enabled");
				this.options.PauseAfterScript = true;
			}
			else if (arg == "f")
			{
				if (!this.enumerator.MoveNext())
				{
					Error("No input file specified after /f switch");
					return;
				}
				var inputFile = this.enumerator.Current;
				Trace.WriteLine("Input file: '" + inputFile + "'");
				this.options.ImmediatorOptions.CompilerOptions.InputFiles.Add(inputFile);
			}
			else if (arg == "a")
			{
				if (!this.enumerator.MoveNext())
				{
					Error("No argument specified after /a switch");
					return;
				}
				var argument = this.enumerator.Current;
				Trace.WriteLine("Script argument: '" + argument + "'");
				this.options.ImmediatorOptions.ScriptArguments.Add(argument);
			}
			else if (arg == "afs")
			{
				Trace.WriteLine("First input file as script argument");
				this.options.ImmediatorOptions.FirstFileAsArgument = true;
			}
			else if (arg == "aas")
			{
				Trace.WriteLine("All input files as script arguments");
				this.options.ImmediatorOptions.AllFilesAsArguments = true;
			}
			else if (arg == "envfs")
			{
				if (!this.enumerator.MoveNext())
				{
					Error("No variable specified after /envfs switch");
					return;
				}
				var variable = this.enumerator.Current;
				Trace.WriteLine("First input file in env var '" + variable + "'");
				this.options.ImmediatorOptions.FirstFileEnvVar = variable;
			}
			else if (arg == "envas")
			{
				if (!this.enumerator.MoveNext())
				{
					Error("No variable specified after /envas switch");
					return;
				}
				var variable = this.enumerator.Current;
				Trace.WriteLine("All input files in env var '" + variable + "'");
				this.options.ImmediatorOptions.AllFilesEnvVar = variable;
			}
			else if (arg == "nopp")
			{
				Trace.WriteLine("Preprocessor disabled");
				this.options.ImmediatorOptions.CompilerOptions.NoPreprocessor = true;
			}
			else if (arg == "pphelp")
			{
				Trace.WriteLine("Preprocessor help requested");
				this.options.PreprocessorHelp = true;
			}
			else if (arg == "qkhelp")
			{
				Trace.WriteLine("QuickHeader help requested");
				this.options.QuickHeaderHelp = true;
			}
			else if (arg == "automainex")
			{
				Trace.WriteLine("Automain help requested");
				this.options.AutomainHelp = true;
			}
			else if (arg == "exe")
			{
				if (!this.enumerator.MoveNext())
				{
					Error("No file specified after /exe switch");
					return;
				}
				var file = this.enumerator.Current;
				Trace.WriteLine("Output executable set to '" + file + "'");
				this.options.ImmediatorOptions.ExecutableFile = file;
			}
			else if (arg == "noexec")
			{
				Trace.WriteLine("Execution suppressed");
				this.options.ImmediatorOptions.NoExecution = true;
			}
			else if (arg == "scripthelpers")
			{
				Trace.WriteLine("Script Helpers requested");
				this.options.ImmediatorOptions.CompilerOptions.ScriptHelpers = true;
			}
			else if (arg == "scripthelpershelp")
			{
				Trace.WriteLine("Script Helpers help requested");
				this.options.ScriptHelpersHelp = true;
			}
			else if (arg == "nocompilesuccess")
			{
				Trace.WriteLine("Compilation success message suppressed");
				this.options.NoCompilationSuccessfulMessage = true;
			}
			else
			{
				Error("Unknown switch: '" + arg + "'");
			}
		}

		private void Error(string message)
		{
			Trace.WriteLine(message);
			this.options.UsageErrors.Add(message);
		}
	}
}
